
/* GCSx
** POPUP.CPP
**
** Popup and drop-down menus
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

const char* PopupItem::accelLabelsLower[ACCEL_LABEL_LOWER_LAST - ACCEL_LABEL_LOWER_FIRST + 1] = {
    // (starts at 8)
    "Backspace",
    "Tab",
    NULL, NULL, NULL, // 10-12
    "Enter",
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 14-21
    NULL, NULL, NULL, NULL, NULL, // 22-26
    "Escape",
    NULL, NULL, NULL, NULL, // 28-31
    "Space",
    "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", "<", "-", ">", "/",
    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
    ":", ";", "<", "+", ">", "?", "@",
    "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
    "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
    "[", "\\", "]", "^", "_", "`",
    "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
    "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
    "{", "|", "}", "~",
    "Delete"
};

const char* PopupItem::accelLabelsUpper[ACCEL_LABEL_UPPER_LAST - ACCEL_LABEL_UPPER_FIRST + 1] = {
    // (starts at 273)
    "Up", "Down", "Right", "Left",
    "Insert", "Home", "End", "PageUp", "PageDown",
    "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
    "F13", "F14", "F15"
};

const string PopupMenu::wtSubmenu("e");
const string PopupMenu::emptyItem("(empty)");
int PopupMenu::checkboxSize = 0;

void PopupItem::prepAccellabel(Sint32 pKey) { start_func
    const char* key = NULL;
    int pKeyMod = (pKey >> 16) & 0xFFFF;
    int pKeySym = pKey & 0xFFFF;

    if ((pKeySym >= ACCEL_LABEL_LOWER_FIRST) && (pKeySym <= ACCEL_LABEL_LOWER_LAST)) {
        key = accelLabelsLower[pKeySym - ACCEL_LABEL_LOWER_FIRST];
    }
    else if ((pKeySym >= ACCEL_LABEL_UPPER_FIRST) && (pKeySym <= ACCEL_LABEL_UPPER_LAST)) {
        key = accelLabelsUpper[pKeySym - ACCEL_LABEL_UPPER_FIRST];
    }
    
    if (key) {
        accelLabel = "(";
        if (pKeyMod & KMOD_SHIFT) accelLabel += "Shift+";
        if (pKeyMod & KMOD_CTRL) accelLabel += "Ctrl+";
        if (pKeyMod & KMOD_ALT) accelLabel += "Alt+";
        accelLabel += key;
        accelLabel += ")";
    }
    else {
        accelLabel = blankString;
    }
    
    // Certain keys become modified for actual accellerator
    // Letters always lowercase
    if ((pKeySym >= 'A') && (pKeySym <= 'Z')) pKeySym += 'a' - 'A';
    else {
        // Punctuation on 0-9 becomes shifted 0-9
        // Punctuation on other keys becomes unshifted version
        switch (pKeySym) {
            case '!': pKeySym = 1; pKeyMod |= KMOD_SHIFT; break;
            case '@': pKeySym = 2; pKeyMod |= KMOD_SHIFT; break;
            case '#': pKeySym = 3; pKeyMod |= KMOD_SHIFT; break;
            case '$': pKeySym = 4; pKeyMod |= KMOD_SHIFT; break;
            case '%': pKeySym = 5; pKeyMod |= KMOD_SHIFT; break;
            case '^': pKeySym = 6; pKeyMod |= KMOD_SHIFT; break;
            case '&': pKeySym = 7; pKeyMod |= KMOD_SHIFT; break;
            case '*': pKeySym = 8; pKeyMod |= KMOD_SHIFT; break;
            case '(': pKeySym = 9; pKeyMod |= KMOD_SHIFT; break;
            case ')': pKeySym = 0; pKeyMod |= KMOD_SHIFT; break;

            case '~': pKeySym = '`'; break;
            case '_': pKeySym = '-'; break;
            case '+': pKeySym = '='; break;
            case '{': pKeySym = '['; break;
            case '}': pKeySym = ']'; break;
            case '|': pKeySym = '\\'; break;
            case ':': pKeySym = ';'; break;
            case '"': pKeySym = '\''; break;
            case '<': pKeySym = ','; break;
            case '>': pKeySym = '.'; break;
            case '?': pKeySym = '/'; break;
        }
    }
    
    accel = pKey;
}
    
const string& PopupItem::getLabel() const { start_func
    return label;
}

void PopupItem::setLabel(const string& newLabel) { start_func
    label = newLabel;
    underline = convertGuiText(&label, &shortcut);
    if (label.length() > POPUP_MAX_WIDTH) label = label.substr(0, POPUP_MAX_WIDTH) + "...";
}

PopupItem::PopupItem() { start_func
}

PopupItem::PopupItem(int pCode, const char* pLabel, Sint32 pKey, int dynamic, class PopupMenu* pSubmenu) { start_func
    code = pCode;
    if (pLabel == NULL) {
        label = "";
        state = POPUP_SEPARATOR;
    }
    else {
        setLabel(pLabel);
        state = POPUP_NONE;
    }
    if (dynamic) state = (PopupState)(state | POPUP_DYNAMIC);
    submenu = pSubmenu;
    prepAccellabel(pKey);
}

PopupMenu::PopupMenu(int menubar) : Window(), items() { start_func
    isMenubar = menubar;
    fromMenubar = 0;
    
    numItems = 0;
    
    checkboxSize = fontHeight() * 4 / 5;
}

void PopupMenu::add(PopupItem& item) { start_func
    items.push_back(item);
    ++numItems;
}

PopupMenu::~PopupMenu() { start_func
    desktop->removeWindow(this);

    vector<PopupItem>::iterator end = items.end();
    for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos) {
        delete (*pos).submenu;
    }
}

#ifndef NDEBUG
const char* PopupMenu::debugDump() const { start_func
    return (isMenubar ? "MENUBAR" : "POPUP");
}
#endif

void PopupMenu::predraw(int xPos, int yPos) { start_func
    currSel = -1;

    int newWidth = 0;
    int newHeight = GUI_POPUP_EDGEPAD;
    int largestItem = 0;
    int largestAccel = 0;

    if (isMenubar) {
        xPos = 0;
        yPos = 0;
        newWidth = screenWidth;
        newHeight = GUI_MENUBAR_TOPPAD + GUI_MENUBAR_BOTTOMPAD + fontHeight(FONT_MENU);
        isEmpty = 0;

        int xPos = GUI_MENUBAR_SIDEPAD + GUI_MENUBAR_LEFTPAD;

        vector<PopupItem>::iterator end = items.end();
        for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos) {
            (*pos).pos = xPos;
            xPos += GUI_MENUBAR_SIDEPAD * 2 + fontWidth((*pos).getLabel(), FONT_MENU);
        }
    }
    else {
        // Calculate size, determine disabled and checkbox states and Y positions
        Window* win = desktop->findPreviousFocusWindow();
        // Hide any separators at top of menu
        int hideSeparator = -1;
        int numPos = 0;

        vector<PopupItem>::iterator end = items.end();
        for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos, ++numPos) {
            PopupItem& currItem = *pos;

            if (currItem.state & PopupItem::POPUP_DYNAMIC) {
                // Clear all temporary states
                currItem.state = PopupItem::POPUP_DYNAMIC;
                Window::CommandSupport support = Window::COMMAND_HIDE;
                // Window, if any, gets to "vote" on this item
                if (win) support = win->supportsCommand(currItem.code);
                // Desktop calls global handler to "vote" on this item also
                support = desktop->supportsCommand(currItem.code, support);
                // Hidden?
                if (support == Window::COMMAND_HIDE) currItem.state = (PopupItem::PopupState)(currItem.state | PopupItem::POPUP_HIDDEN);
                // Grey?
                if (!(support & Window::COMMAND_ENABLE)) currItem.state = (PopupItem::PopupState)(currItem.state | PopupItem::POPUP_GRAYED);
                // Add check/radio?
                if (support & Window::COMMAND_CHECKBOX) currItem.state = (PopupItem::PopupState)(currItem.state | PopupItem::POPUP_CHECKBOX);
                if (support & Window::COMMAND_RADIO) currItem.state = (PopupItem::PopupState)(currItem.state | PopupItem::POPUP_RADIO);
                if (support & Window::COMMAND_SELECTED) currItem.state = (PopupItem::PopupState)(currItem.state | PopupItem::POPUP_CHECKED);
            }
            
            // Y position
            currItem.pos = newHeight;
            
            if (currItem.state & PopupItem::POPUP_SEPARATOR) {
                if (hideSeparator) {
                    // Hide
                    currItem.state = (PopupItem::PopupState)(currItem.state | PopupItem::POPUP_HIDDEN);
                    continue;
                }
                // Ensure unhidden
                currItem.state = (PopupItem::PopupState)(currItem.state & ~PopupItem::POPUP_HIDDEN);
                newHeight += GUI_POPUP_SEPHEIGHT;
                // Hide any separators after this one; note which separator was last
                // (this will never be 0, because a separator at pos 0 would be hidden)
                hideSeparator = numPos;
            }
            else if (currItem.state & PopupItem::POPUP_HIDDEN) continue;
            else {
                newHeight += fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD;
                // Real item- can show separators again
                hideSeparator = 0;
            }

            int size = fontWidth(currItem.getLabel(), FONT_MENU);
            if (size > largestItem) largestItem = size;
            size = fontWidth(currItem.accelLabel, FONT_MENU);
            if (size > largestAccel) largestAccel = size;
        }
        
        // Hide last separator?
        if (hideSeparator > 0) {
            items[hideSeparator].state = (PopupItem::PopupState)(items[hideSeparator].state | PopupItem::POPUP_HIDDEN);
            newHeight -= GUI_POPUP_SEPHEIGHT;
        }
        
        if (newHeight > GUI_POPUP_EDGEPAD) {
            isEmpty = 0;
            if (largestAccel) newWidth = largestItem + GUI_POPUP_GUTTER + largestAccel;
            else newWidth = largestItem;
            accelOffset = largestItem + GUI_POPUP_GUTTER;
        }
        else {
            // If no items, create room for one (empty) fake item
            isEmpty = 1;
            newWidth = fontWidth(emptyItem, FONT_MENU);
            newHeight += fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD;
            accelOffset = 0;
        }

        newWidth += GUI_POPUP_EDGEPAD * 2 + GUI_POPUP_MARGIN * 2;
        newHeight += GUI_POPUP_EDGEPAD + GUI_POPUP_LINEPAD;
    
        // Move as needed
        if (xPos + newWidth > screenWidth) xPos = screenWidth - newWidth;
        if (yPos + newHeight > screenHeight) yPos = screenHeight - newHeight;
        if (xPos < 0) xPos = 0;
        if (yPos < 0) yPos = 0;
    
        // Checkbox size/position
        checkboxX = (GUI_POPUP_MARGIN - fontHeight(FONT_WIDGET)) / 2;
        checkboxY = fontAscent(FONT_MENU) - fontAscent(FONT_WIDGET);
    }

    move(xPos, yPos);
    resize(newWidth, newHeight);        
}

void PopupMenu::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
    assert(destSurface);

    if (visible) {
        // If dirty, redraw all
        if (dirty) {
            getRect(toDisplay);
            toDisplay.x += xOffset;
            toDisplay.y += yOffset;
            dirty = 0;
            intersectRects(toDisplay, clipArea);
        }
        
        // Anything to draw?
        if (toDisplay.w) {
            SDL_SetClipRect(destSurface, &toDisplay);
        
            xOffset += x;
            yOffset += y;

            // This doesn't attempt to skip items outside dirty area, but they
            // will be clipped away when drawn.

            // Background
            if (isMenubar) {
                drawRect(xOffset, yOffset, width, height - 2, guiPacked[COLOR_FILL], destSurface);
                drawHLine(xOffset, yOffset + width - 1, height - 2, guiPacked[COLOR_DARK1], destSurface);
                drawHLine(xOffset, yOffset + width - 1, height - 1, guiPacked[COLOR_DARK2], destSurface);
            }
            else {
                drawGuiBox(xOffset, yOffset, width, height, 2, destSurface);
            }

            // Draw all popup entries we can see
            if (isEmpty) {
                drawText(emptyItem, guiRGB[COLOR_LIGHT1], xOffset + GUI_POPUP_EDGEPAD + GUI_POPUP_MARGIN + 1, yOffset + GUI_POPUP_EDGEPAD + 1, destSurface, FONT_MENU);
                drawText(emptyItem, guiRGB[COLOR_DARK1], xOffset + GUI_POPUP_EDGEPAD + GUI_POPUP_MARGIN, yOffset + GUI_POPUP_EDGEPAD, destSurface, FONT_MENU);
            }
            else {
                int numPos = 0;            
                vector<PopupItem>::iterator end = items.end();
                for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos, ++numPos) {
                    PopupItem& currItem = *pos;
                    
                    if (currItem.state & PopupItem::POPUP_HIDDEN) continue;

                    int xPos, yPos, xOffs;
                    if (isMenubar) {
                        xPos = xOffset + currItem.pos;
                        xOffs = 0;
                        yPos = yOffset + GUI_MENUBAR_TOPPAD;
                    }
                    else {
                        xPos = xOffset + GUI_POPUP_EDGEPAD;
                        xOffs = GUI_POPUP_MARGIN;
                        yPos = yOffset + currItem.pos;
                    }
                
                    // Separators
                    if (currItem.state & PopupItem::POPUP_SEPARATOR) {
                        drawGuiBoxInvert(xPos, yPos + GUI_POPUP_SEPHEIGHT / 2, width - GUI_POPUP_EDGEPAD * 2, GUI_POPUP_SEPLINE, 1, destSurface);
                    }
                    else {
                        // Border of selection
                        if (currSel == numPos) {
                            if (isMenubar) {
                                // Non-gradient: nothing
                                drawGradient(xPos - GUI_MENUBAR_SIDEPAD, yPos, fontWidth(currItem.getLabel(), FONT_MENU) + GUI_MENUBAR_SIDEPAD * 2, fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD + 1, guiRGB[COLOR_POPUPFILL1], guiRGB[COLOR_POPUPFILL2], destSurface);
                                drawPixel(xPos - GUI_MENUBAR_SIDEPAD + fontWidth(currItem.getLabel(), FONT_MENU) + GUI_MENUBAR_SIDEPAD * 2 - 1, yPos, guiPacked[COLOR_FILL], destSurface);
                                drawPixel(xPos - GUI_MENUBAR_SIDEPAD, yPos + fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD, guiPacked[COLOR_FILL], destSurface);
                                drawGuiBoxInvert(xPos - GUI_MENUBAR_SIDEPAD, yPos, fontWidth(currItem.getLabel(), FONT_MENU) + GUI_MENUBAR_SIDEPAD * 2, fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD + 1, 1, destSurface);
                            }
                            else {
                                // Non-gradient: nothing
                                drawGradient(xPos, yPos, width - GUI_POPUP_EDGEPAD * 2, fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD + 1, guiRGB[COLOR_POPUPFILL1], guiRGB[COLOR_POPUPFILL2], destSurface);
                                drawPixel(xPos + width - GUI_POPUP_EDGEPAD * 2 - 1, yPos, guiPacked[COLOR_FILL], destSurface);
                                drawPixel(xPos, yPos + fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD, guiPacked[COLOR_FILL], destSurface);
                                drawGuiBoxInvert(xPos, yPos, width - GUI_POPUP_EDGEPAD * 2, fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD + 1, 1, destSurface);
                            }
                        }
                        
                        // Text, including checkbox/arrow/underline
                        if (currItem.state & PopupItem::POPUP_GRAYED) {
                            if (currItem.state & PopupItem::POPUP_CHECKBOX) {
                                drawCheckbox(0, currItem.state & PopupItem::POPUP_CHECKED, 1, xPos + checkboxX, yPos + checkboxY, checkboxSize, destSurface);
                            }
                            else if (currItem.state & PopupItem::POPUP_RADIO) {
                                drawCheckbox(1, currItem.state & PopupItem::POPUP_CHECKED, 1, xPos + checkboxX, yPos + checkboxY, checkboxSize, destSurface);
                            }
                            if ((currItem.submenu != NULL) && (!isMenubar)) {
                                drawText(wtSubmenu, guiRGB[COLOR_LIGHT1], xPos + width - checkboxX - GUI_POPUP_EDGEPAD * 2 - fontWidth(wtSubmenu, FONT_WIDGET), yPos + checkboxY + 1, destSurface, FONT_WIDGET);
                                drawText(wtSubmenu, guiRGB[COLOR_DARK1], xPos + width - checkboxX - GUI_POPUP_EDGEPAD * 2 - fontWidth(wtSubmenu, FONT_WIDGET), yPos + checkboxY, destSurface, FONT_WIDGET);
                            }
                            if (currSel != numPos) {
                                drawText(currItem.getLabel(), guiRGB[COLOR_LIGHT1], xPos + xOffs + 1, yPos + 1, destSurface, FONT_MENU);
                                drawText(currItem.accelLabel, guiRGB[COLOR_LIGHT1], xPos + xOffs + 1 + accelOffset, yPos + 1, destSurface, FONT_MENU);
                            }
                            drawText(currItem.getLabel(), guiRGB[COLOR_DARK1], xPos + xOffs, yPos, destSurface, FONT_MENU);
                            drawText(currItem.accelLabel, guiRGB[COLOR_DARK1], xPos + xOffs + accelOffset, yPos, destSurface, FONT_MENU);
                        }
                        else {
                            if (currItem.state & PopupItem::POPUP_CHECKBOX) {
                                drawCheckbox(0, currItem.state & PopupItem::POPUP_CHECKED, 0, xPos + checkboxX, yPos + checkboxY, checkboxSize, destSurface);
                            }
                            else if (currItem.state & PopupItem::POPUP_RADIO) {
                                drawCheckbox(1, currItem.state & PopupItem::POPUP_CHECKED, 0, xPos + checkboxX, yPos + checkboxY, checkboxSize, destSurface);
                            }
                            if ((currItem.submenu != NULL) && (!isMenubar)) {
                                drawText(wtSubmenu, guiRGB[currSel == numPos ? COLOR_POPUPTEXT : COLOR_TEXT], xPos + width - checkboxX - GUI_POPUP_EDGEPAD * 2 - fontWidth(wtSubmenu, FONT_WIDGET), yPos + checkboxY, destSurface, FONT_WIDGET);
                            }
                            drawText(currItem.getLabel(), guiRGB[currSel == numPos ? COLOR_POPUPTEXT : COLOR_TEXT], xPos + xOffs, yPos, destSurface, FONT_MENU);
                            drawText(currItem.accelLabel, guiRGB[currSel == numPos ? COLOR_POPUPTEXT : COLOR_TEXT], xPos + xOffs + accelOffset, yPos, destSurface, FONT_MENU);
                            if (currItem.underline) drawTextUnderline(currItem.underline, guiPacked[currSel == numPos ? COLOR_POPUPTEXT : COLOR_TEXT], xPos + xOffs, yPos, destSurface, FONT_MENU);
                        }
                    }
                }
            }
        }
    }
}

// (close all open (popup) submenus at a certain level or higher)
int PopupMenu::closeAllSubmenus(int level) { start_func
    PopupMenu* next;
    int pos = 0;
    int count = 0;
    // (assignment intentional)
    while ( (next = dynamic_cast<PopupMenu*>(desktop->findWindow(WINDOW_POPUPMENU, pos))) ) {
        if (next->submenuLevel >= level) {
            desktop->removeWindow(next);
            next->resize(0, 0);
            ++count;
        }
        else ++pos;
    }
    return count;
}

void PopupMenu::popup(int xPos, int yPos, int newSubmenuLevel, int isFromMenubar) { start_func
    assert(!isMenubar);

    submenuLevel = newSubmenuLevel;
    fromMenubar = isFromMenubar;
    predraw(xPos, yPos);
    setDirty();
    desktop->addWindow(this, 1);
}

void PopupMenu::menubar(int show) { start_func
    assert(isMenubar);

    submenuLevel = -1;
    if (show) {
        predraw(0, 0);
        setDirty();
        desktop->addWindow(this);
    }
    else {
        desktop->removeWindow(this);
        resize(0, 0);
    }
}

int PopupMenu::wantsToBeDeleted() const { start_func
    return !isMenubar;
}

void PopupMenu::resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp) { start_func
    if (isMenubar) {
        if (visible) predraw(0, 0);
    }
    else {
        Window::resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);
    }
}

int PopupMenu::event(int hasFocus, const SDL_Event* event) { start_func
    assert(event);

    int prevSel = currSel;
    int expand = 0;
    int select = 0;
    int item = -1;
    Sint32 key = 0;
    int used = 0;

    switch (event->type) {
        case SDL_CLOSE:
            if (isMenubar) {
                // Close all submenus and ourself
                closeAllSubmenus(0);
                menubar(0);
            }
            else {
                // Close all submenus at or below our submenu level
                closeAllSubmenus(submenuLevel);
            }
            return 1;

        case SDL_MOUSEBUTTONDBL:
        case SDL_MOUSEBUTTONDOWN:
        case SDL_MOUSEBUTTONUP:
            if (event->button.button != SDL_BUTTON_LEFT) return 0;
        case SDL_MOUSEMOTION:
            int targetX, targetY;
            
            if (event->type == SDL_MOUSEMOTION) {
                targetX = event->motion.x;
                targetY = event->motion.y;
            }
            else {
                targetX = event->button.x;
                targetY = event->button.y;
            }

            if (isMenubar) {
                // Don't cancel selection on menubar if a popup is open
                if ((desktop->findFocusWindow() == NULL) || (desktop->findFocusWindow()->windowType() != WINDOW_POPUPMENU)) currSel = -1;
            
                if ((targetY > GUI_MENUBAR_TOPPAD) && (targetY <= fontHeight(FONT_MENU) + GUI_MENUBAR_TOPPAD + GUI_POPUP_LINEPAD)) {
                    int row = targetX -= GUI_MENUBAR_LEFTPAD;
                    if (row >= 0) {
                        int numPos = 0;
                        vector<PopupItem>::iterator end = items.end();
                        for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos, ++numPos) {
                            PopupItem& currItem = *pos;

                            if (currItem.state & PopupItem::POPUP_HIDDEN) continue;
                            int size = fontWidth(currItem.getLabel(), FONT_MENU) + GUI_MENUBAR_SIDEPAD * 2;
                            if (row < size) {
                                currSel = numPos;
                                break;
                            }
                            else {
                                row -= size;
                            }
                        }
                    }
                }
                
                // If nothing selected, refuse focus
                if (currSel == -1) {
                    desktop->sendToBottom(this);
                }        
            }
            else {
                currSel = -1;
                        
                if ((targetX > GUI_POPUP_EDGEPAD) && (targetX < width - GUI_POPUP_EDGEPAD)) {
                    int row = targetY - GUI_POPUP_EDGEPAD;
                    if (row >= 0) {
                        int numPos = 0;
                        vector<PopupItem>::iterator end = items.end();
                        for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos, ++numPos) {
                            PopupItem& currItem = *pos;

                            if (currItem.state & PopupItem::POPUP_HIDDEN) continue;
                            if (currItem.state & PopupItem::POPUP_SEPARATOR) {
                                if (row < GUI_POPUP_SEPHEIGHT) break;
                                row -= GUI_POPUP_SEPHEIGHT;                                
                            }
                            else {
                                if (row < (fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD)) {
                                    currSel = numPos;
                                    break;
                                }
                                else {
                                    row -= fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD;
                                }
                            }
                        }
                    }
                }
            }
            
            if ((event->type == SDL_MOUSEBUTTONDOWN) && (event->button.button == SDL_BUTTON_LEFT)) {
                expand = 1;
            }

            if ((event->type == SDL_MOUSEBUTTONUP) && (event->button.button == SDL_BUTTON_LEFT)) {
                expand = 1;
                if (currSel != -1) select = 1;
            }
            
            used = 1;
            break;
            
        case SDL_MOUSEFOCUS:
            if (event->user.code & 1) return 0;
            else {
                // Cancel selection unless this is menubar and a popup is open
                if ((!isMenubar) || (desktop->findFocusWindow() == NULL) || (desktop->findFocusWindow()->windowType() != WINDOW_POPUPMENU)) currSel = -1;
                // Refuse input focus if menubar
                if (isMenubar) desktop->sendToBottom(this);       
            }

            used = 1;
            break;
            
        case SDL_INPUTFOCUS:
            if (event->user.code & 1) return 0;
            else {
                if ((desktop->findFocusWindow() == NULL) ||
                    ((desktop->findFocusWindow()->windowType() != WINDOW_POPUPMENU) &&
                     (desktop->findFocusWindow()->windowType() != WINDOW_MENUBAR))
                   ) {
                    currSel = -1;
                    select = 1;
                }
            }

            used = 1;
            break;
        
        case SDL_SYSKEY:
            // If ALT being held and we're the menubar, scan for shortcut
            if ((event->key.keysym.mod == KMOD_ALT) && (isMenubar)) {
                key = event->key.keysym.sym;
            }

        case SDL_KEYDOWN:
            if (event->type != SDL_SYSKEY) key = event->key.keysym.sym;
            key = combineKey(key, event->key.keysym.mod);
        
            // Translate keys for menubar
            if (isMenubar) {
                switch (key) {
                    case SDLK_DOWN:
                        key = SDLK_RIGHT;
                        break;
                    case SDLK_LEFT:
                        key = SDLK_UP;
                        break;
                    case SDLK_RIGHT:
                        key = SDLK_DOWN;
                        break;
                }
            }
        
            switch (key) {
                case SDLK_ESCAPE:
                    // If menubar, no current selection, and no submenus close,
                    // ignore key (allows ESC to filter through in game mode)
                    if ((isMenubar) && (currSel == -1) && (closeAllSubmenus(0) == 0))
                        break;

                    used = 1;
                    item = 0;
                    break;
                    
                case SDLK_UP:
                    
                    used = 1;

                    do {
                        if (currSel < 1) currSel = numItems - 1;
                        else --currSel;
                        if (currSel == prevSel) break;
                    } while (items[currSel].state & (PopupItem::POPUP_SEPARATOR | PopupItem::POPUP_HIDDEN));
                    break;
                    
                case SDLK_DOWN:
                    if (isEmpty) return 0;

                    used = 1;

                    do {
                        if (currSel < 0) currSel = 0;
                        else if (++currSel >= numItems) currSel = 0;
                        if (currSel == prevSel) break;
                    } while (items[currSel].state & (PopupItem::POPUP_SEPARATOR | PopupItem::POPUP_HIDDEN));
                    break;

                case SDLK_RIGHT:
                    used = 1;
                    expand = 1;

                    if (currSel < 0) expand = 0;
                    else if  (items[currSel].submenu == NULL) expand = 0;
                    
                    if ((!expand) && (fromMenubar)) {
                        // Go to one item right on menubar
                        closeAllSubmenus(0);

                        PopupMenu* next;
                        // (assignment intentional)
                        if ( (next = dynamic_cast<PopupMenu*>(desktop->findWindow(WINDOW_MENUBAR, 0))) ) {
                            SDL_Event myEvent = *event;
                            myEvent.key.keysym.sym = SDLK_DOWN;
                            // Activate, send RIGHT, then DOWN
                            desktop->bringToTop(next);
                            next->event(0, event);
                            return next->event(0, &myEvent);
                        }
                        
                        return 1;
                    }

                    break;

                case SDLK_LEFT:
                    used = 1;

                    if (submenuLevel >= 0) {
                        // Activate submenu above this, if there is one
                        PopupMenu* next;
                        int pos = 0;
                        // (assignment intentional)
                        while ( (next = dynamic_cast<PopupMenu*>(desktop->findWindow(WINDOW_POPUPMENU, pos))) ) {
                            if (next->submenuLevel == submenuLevel - 1) {
                                // Close all submenus at or below our level
                                closeAllSubmenus(submenuLevel);
                                desktop->bringToTop(next);
                                return 1;
                            }
                            else pos++;
                        }
                        
                        // Go to one item left on menubar?
                        if (fromMenubar) {
                            closeAllSubmenus(0);
    
                            // (assignment intentional)
                            if ( (next = dynamic_cast<PopupMenu*>(desktop->findWindow(WINDOW_MENUBAR, 0))) ) {
                                SDL_Event myEvent = *event;
                                myEvent.key.keysym.sym = SDLK_DOWN;
                                // Activate, send LEFT, then DOWN
                                desktop->bringToTop(next);
                                next->event(0, event);
                                return next->event(0, &myEvent);
                            }
                        }
                            
                        return 1;
                    }
                    break;

                case SDLK_RETURN:
                case SDLK_KP_ENTER:
                    used = 1;
                    expand = 1;
                    select = 1;
                    break;
                    
                default:
                    // Look for appropriate shortcut key; remove ALT
                    key &= ~(KMOD_ALT << 16);
                    // (make sure there's still a key)
                    if (key) {
                        int numPos = 0;
                        vector<PopupItem>::iterator end = items.end();
                        for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos, ++numPos) {
                            PopupItem& currItem = *pos;

                            // Grayed includes Temphidden
                            if (currItem.state & (PopupItem::POPUP_HIDDEN | PopupItem::POPUP_SEPARATOR | PopupItem::POPUP_GRAYED)) continue;
                            if (currItem.shortcut == key) {
                                used = 1;
                                currSel = numPos;
                                expand = 1;
                                select = 1;
                                break;
                            }
                        }
                    }
                    break;
            }
            
            break;
            
        default:
            return 0;
    }

    if (currSel != prevSel) {
        // Selection changed and not empty- close submenus, open this one if any closed
        if (currSel != -1) {
            if (closeAllSubmenus(submenuLevel + 1)) expand = 1;
        }
        setDirty();
    }

    if ((expand) && (!isEmpty) && (currSel >= 0) && (items[currSel].submenu != NULL) && (!(items[currSel].state & PopupItem::POPUP_GRAYED))) {
        // Close all submenus at or below the submenu level
        closeAllSubmenus(submenuLevel + 1);
        
        if (isMenubar) {
            items[currSel].submenu->popup(x + items[currSel].pos - GUI_MENUBAR_SIDEPAD, y + fontHeight(FONT_MENU) + GUI_MENUBAR_TOPPAD + GUI_POPUP_LINEPAD + 1, submenuLevel + 1, 1);
        }
        else {
            items[currSel].submenu->popup(x + width - GUI_POPUP_EDGEPAD, y + items[currSel].pos - GUI_POPUP_EDGEPAD, submenuLevel + 1, fromMenubar);
        }
        return 1;
    }
    
    else if (select) {
        if ((currSel < 0) || (isEmpty)) item = 0;
        else if (items[currSel].state & PopupItem::POPUP_GRAYED) item = 0;
        else item = items[currSel].code;
    }
    
    if (item >= 0) {
        // Close all popup menus at any level
        closeAllSubmenus(0);
        
        // If we're menubar, refuse focus
        if (isMenubar) {                  
            desktop->sendToBottom(this);       
        }        
        
        // Tell menubar to cancel showing current selection
        PopupMenu* next;
        // (assignment intentional)
        if ( (next = dynamic_cast<PopupMenu*>(desktop->findWindow(WINDOW_MENUBAR, 0))) ) {
            SDL_Event myEvent = *event;
            myEvent.type = SDL_MOUSEFOCUS;
            myEvent.user.code = 0;
            next->event(0, &myEvent);
        }
        
        // Send event for command selected
        if (item > 0) {
            desktop->broadcastEvent(SDL_COMMAND, item);
            desktop->broadcastEvent(SDL_COMMAND, CMD_RELEASE);
        }
        
        return 1;
    }
    
    return used;
}

int PopupMenu::isDocked() const { start_func
    return isMenubar;
}

int PopupMenu::tempFocus() const { start_func 
    return 1;
}

Window::WindowType PopupMenu::windowType() const { start_func 
    return isMenubar ? WINDOW_MENUBAR : WINDOW_POPUPMENU;
}

Window::WindowSort PopupMenu::windowSort() const { start_func 
    return isMenubar ? WINDOWSORT_DOCKED : WINDOWSORT_POPUP;
}

PopupMenu* PopupMenu::compileMenu(const PopupGen* menuData, int isMenuBar, int* pos) { start_func
    PopupMenu* myMenu = NULL;
    PopupMenu* subMenu = NULL;
    PopupItem* subItem = NULL;
    int dummyPos = 0;
    if (!pos) pos = &dummyPos;

    myMenu = new PopupMenu(isMenuBar);
 
    while (menuData[*pos].code) {
        // Submenu
        if (menuData[*pos].code == MENU_SUB) {
            int oldPos = *pos;
            
            ++*pos;
            subMenu = compileMenu(menuData, 0, pos);
            subItem = new PopupItem(menuData[oldPos].dynamic, menuData[oldPos].name,
                                    0, menuData[oldPos].dynamic ? 1 : 0, subMenu);
            // subMenu not "owned" by anything yet
        }
        // Separator
        else if (menuData[*pos].code == MENU_SEP) {
            subItem = new PopupItem(0, NULL, 0, 0);
        }
        // Standard menu item
        else {
            subItem = new PopupItem(menuData[*pos].code, menuData[*pos].name,
                                    config->readShortcut(menuData[*pos].code),
                                    menuData[*pos].dynamic);
        }
        
        myMenu->add(*subItem);
        // subMenu now owned by myMenu
        subMenu = NULL;
        delete subItem;
        subItem = NULL;
        
        ++*pos;
    }
    
    return myMenu;
}
